Odnajdywanie zawieszonych workflowów z użyciem Microsoft Flow
Table of Contents
W mojej codziennej pracy nad procesami biznesowymi w Office 365, konkretnie w SharePoint Online jedna rzecz denerwuje mnie najbardziej – mianowicie brak mechanizmów informowania mnie, że workflow się zawiesił, czyli jest w stanie „Suspended”.
Rozwiązania nie dostarcza ani Microsoft, ani też Nintex, z którym pracuję od dawna. Istnieją jedynie obejścia problemu, jednak okazują się niewystarczające, mnie zaś zależało na tym, by móc reagować proaktywnie, a nie reaktywnie na każde zdażenie zawieszenia się przepływu.
„Suspended” – co to takiego?
W sytuacji, gdy przepływ pracy nie ma dostępu do danego zasobu (z powodu braku uprawnień, lub faktu iż zasób nie istnieje), albo nie może dostać się do zdalnej usługi, albo nie może przekształcić jednego typu danych na inny – zazwyczaj „zawiesza” się – czyli ustawia się w stanie, w którym oczekuje na akcję administratora. Administrator w takim wypadku powinien rozwiązać problem, na który natrafił przepływ, a następnie go wznowić.
O ile w SharePoint on-premise ten status jest jawnie komunikowany i faktycznie, nawet narzędzie analityczne Nintex Hawkeye jest w stanie wychwycić ten fakt i odnotować go w prezentowanych danych, o tyle w SharePoint Online sytuacja ta się znacznie komplikuje.
Okazuje się bowiem, iż w SPO jedynie status wewnętrzny przepływu pracy zmienia się na „Suspended”, jednak nie zmienia się status dla użytkownika i w efekcie tego workflow nadal jest postrzegany jako działający.
Workaround? Proszę bardzo!
Skoro już wiedziałem, że nie mam co liczyć na rozwiązania firm trzecich, zacząłem szukać możliwych rozwiązań alternatywnych. W zasadzie udało mi się znaleźć tylko jedno – operujące na alertach i listach historii przepływu pracy.
Mianowicie chodzi tu o to, by na liście Historii (z reguły jest to lista Workflow History, dostępna pod adresem: http://sharepoint/site/lists/Workflow%20History) stworzyć dedykowany widok, który będzie pokazywać tylko i wyłącznie te elementy, które w „Description” będą zawierać określone frazy. Ja używam „error”, „exception”, „not found”, „failed”:
Na końcu należy ustawić sobie jeszcze alert, dla tego widoku, by w przypadku pojawienia się nowego rekordu otrzymać powiadomienie. Niby ok, ale:
- powodów zawieszenia się przepływu jest więcej, co ważne – bardzo często nie pojawia się przy tym żaden wpis w historii przepływu.
- mając wiele procesów w różnych witrynach, trzeba tworzyć bardzo dużo takich widoków, co z kolei przekłada się na brak możliwości łatwego zarządzania tym.
- element w workflow history nie mówi nam wprost, z jakiej listy pochodzi element, którego dotyczy problem – ciężko znaleźć stronę ze statusem zawieszonego przepływu.
Może PowerShell? Albo CSOM?
Szukając rozwiązań natrafiłem oczywiście na różne rozwiązania korzystające z dobrodziejstw PowerShell lub CSOM. Jednak choćbym bardzo chciał ich użyć, nie udało mi się ich uruchomić w środowisku SharePoint Online.
SP.WorkflowServices REST API
I tak, szukając rozwiązań wpadł mi do głowy pomysł użycia REST API. A jak REST, to i Microsoft Flow 🙂 Scenariusz miał być prosty:
- Pobieram token Request Digest
- Pobieram listę witryn
- Dla każdej witryny pobieram listę GUID subskrypcji workflow (/_api/SP.WorkflowServices.WorkflowSubscriptionService.Current/EnumerateSubscriptions())
- Dla każdej subskrypcji pobieram listę aktywnych instancji (
/_api/SP.WorkflowServices. WorkflowInstanceService.Enumerate()) - Dla każdej instancji sprawdzam status – jeśli = 2 – oznacza, że zawieszony.
Możliwe statusy wewnętrzne instancji
NotStarted : 0, Started : 1, Suspended : 2, Canceling : 3, Canceled : 4, Terminated : 5, Completed : 6, NotSpecified : 7, Invalid : 8
I wszystko byłoby pięknie, gdyby nie fakt, że metoda „Enumerate” dla „WorkflowInstanceService” w SharePoint Online jest… niedostępna! Upewniłem się spradzając listę metod korzystając z URL: /_api/$metadata.
Jak ja to zrobiłem?
Zrozumiałem, że jedyny sposób, by dostać się do instancji, to po prostu enumerować je dla przepływów pracy witryny i każdego z elementów list osobno.
Zdecydowałem się więc na użycie dwóch metod:
- SP.WorkflowServices.WorkflowInstanceService.Current/EnumerateInstancesForSite()
- SP.WorkflowServices.WorkflowInstanceService.Current/EnumerateInstancesForListItem()
Lista konfiguracyjna
W SharePoint stworzyłem listę „administracyjną”, będącą definicją witryn i list, jakie mają być monitorowane na obecność zawieszonych przepływów. Lista przechowuje następujące dane:
- Adres URL witryny do monitorowania
- Nazwy list, rozdzielone średnikami
- GUIDy list, rozdzielone średnikami
Przepływ Microsoft Flow
W zasadzie są to dwa przepływy:
- Główny, który uruchamiany jest przez harmonogram (w moim przypadku raz, na 4h), który dla każdej witryny pobiera listę subskrypcji oraz bada workflowy w witrynie, a następnie, dla każdej z list wskazanych do monitorowania uruchamia…
- Przepływ dodatkowy, który bada 50 ostatnio zmodyfikowanych na niej elementów.
Przepływ główny
Na początku przepływ pobiera listę witryn i rozdzielonych średnikami danych list do monitorowania, a następnie inicjuje zmienne używane w dalszym działaniu:
- temp subscription object oraz subscription object – są to dwie zmienne typu „object” używane do budowania obiektu, przechowującego informacje o subskrypcjach przepływów pracy w danej witrynie, w postaci: {„GUID”:”Nazwa”},
- list names oraz list guids – dwie zmienne typu „array”, które trzymają nazwy i GUIDy zdefiniowanych list, zamienionych na listę, z użyciem wyrażenia: „split(nazwy_list_podzielone_srednikami, ’;’)”,
- report variable – zmienna tekstowa, do której są zapisywane informacje o znalezionych, zawieszonych przepływach i która finalnie służy jako treść wysyłanego maila z informacją,
- counter – zmienna numeryczna, która jest używana przez pętlę przechodzącą po wartościach z listy GUIDów list.
Następnie workflow zaczyna działanie dla każdej witryny zdefiniowanej do monitorowania. I tak, dla każdej witryny pobiera token Request Digest (zapytanie POST do _api/contextinfo i ekstrakcja wartości z użyciem akcji „Parse JSON”), a następnie dla każdej znalezionej subskrypcji dodaje ją do parametrów obiektu subscription object:
- Korzystając z wyrażenia: union(addProperty(variables( 'var_TempSubscInstanceName_dict’), item()?[’Id’], item()?[’Name’]), variables(’var_SubscInstanceName_dict’)) tworzę obiekt w zmiennej
temp subscription object, łącząc istniejącą zmienną subscription object z danymi GUID i Name przetwarzanej subskrypcji. - Nadpisuję zmienną subscription object wynikiem działania akcji „Compose”.
Następnie pobieram listę workflowów witryny, będących w statusie „Suspended”:
/_api/SP.WorkflowServices.WorkflowInstanceService.Current/EnumerateInstancesForSite()?$Filter=Status eq 2&$select=Id,FaultInfo,WorkflowSubscriptionId,InstanceCreated,LastUpdated
Dla każdego z nich dodaję do finalnej wiadomości informację o nim: jaką ma nazwę, jaki jest jego adres i powód, dla którego znalazł się w tym stanie.
Nazwę workflowu pobieram z obiektu subscription object – znając GUID przepływu, pobieram jego tytuł:
variables('var_SubscInstanceName_dict')?[item()?['WorkflowSubscriptionId']]
Następnie dla każdej monitorowanej listy, zdefiniowanej dla danej witryny, uruchamiam drugi przepływ, którego zadaniem jest przejrzenie ostatnio zmodyfikowanych, 50-ciu elementów i sprawdzeniu, czy nie ma na nich zawieszonych przepływów.
Uruchamiając przekazuję informacje:
- Adres URL witryny
- Nazwa listy
- GUID listy
- Obiekt GUIDów i nazw subskrypcji
Zmienna counter jest mi potrzebna do tego, by dla każdego obrotu tej pętli, ze zmiennych listowych nazw list i GUIDów pobrać właściwe wartości:
variables('var_ListGuids_coll')?[variables('var_Counter_int')] variables('var_ListNames_coll')?[variables('var_Counter_int')]
Na koniec workflow główny wysyła mi wiadomość e-mail z przygotowaną treścią i listą informacji o zawieszonych workflowach w witrynach i na listach, które zostały przejrzane.
Przepływ dodatkowy
Jego zadaniem jest przeglądanie ostatnio zmodyfikowanych, 50-ciu elementów wskazanej listy. Workflow jest uruchamiany żądaniem POST.
Podobnie jak przepływ główny, tak i dodatkowy najpierw pobiera token Request Digest. Następnie inicjuje zmienne, używane w dalszym działaniu oraz pobiera listę elementów, jakie ma sprawdzić. Zauważ, że pobieram jedynie ID i Title każdego z elementów, sortując je malejąco po dacie modyfikacji:
_api/web/lists(['ListGUID'])/Items()?$select=ID,Title&$orderby=Modified desc&$top=50
Następnie dla każdego elementu, workflow sprawdza, czy przypadkiem nie jest z nim powiązany żaden, zawieszony przepływ pracy. Robię to używając poniższego zapytania:
/_api/SP.WorkflowServices.WorkflowInstanceService.Current/EnumerateInstancesForListItem(listId='['ListGUID']', itemId='['Id']')?$Filter=Status eq 2&$select=Id,FaultInfo,WorkflowSubscriptionId,InstanceCreated,LastUpdated
Następnie, dla każdego, znalezionego wyniku, informacja o nim dodawana jest do wiadomości zwrotnej.
Na koniec workflow zwraca wynik swojego działania – listę elementów i nazw przepływów pracy, które są zawieszone.
Wiadomość e-mail
Przepływ pracy na koniec przesyła do mnie wiadomość, w której widzę, gdzie aktualnie znajdują się zawieszone przepływy pracy i bezpośrednio z niej mogę od razu przejść do strony statusu danego przepływu, aby go wznowić:
Podsumowanie
Omówione rozwiązanie dostarcza mi danych, które pozwalają mi proaktywnie reagować na zawieszanie się przepływów. Nie jestem już stawiany w sytuacji, gdy to klient informuje mnie, że „ten workflow chyba nie działa” – jestem w stanie szybciej go wznowić, dzięki czemu użytkownik końcowy nawet nie zauważa problemu.
Pewnym minusem rozwiązania jest fakt, iż muszę ręcznie utrzymywać listę z nazwami i GUIDami list do monitorowania, jednak jest to praca, której nie wykonuję często.
Co istotne też, workflow dla 12 witryn i dla ok. 20 listy (czyli ok. 1000 rekordów) działa ok. 15 minut.
Jeśli podoba Ci się to rozwiązanie lub masz pytania – zostaw komentarz! 🙂